package system.task.listener;

import framework.storage.FileStorage;
import framework.utils.JsonUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.quartz.*;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.util.StringUtils;
import system.entity.SysTaskDetail;
import system.entity.SysTaskJob;
import system.entity.SysTaskLog;
import system.service.sys.SysTaskDetailService;
import system.service.sys.SysTaskLogService;
import system.task.Task;
import system.task.TaskLogger;
import system.task.config.TaskConfig;
import system.task.defined.TaskRunStatus;
import system.task.events.TaskFinishedEvent;
import system.task.events.TaskStartedEvent;
import system.task.utils.TaskUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

/**
 * 任务监听器
 */
@Slf4j
public class TaskListener implements JobListener {

    @Override
    public String getName() {
        return TaskListener.class.getSimpleName();
    }

    private final ApplicationEventPublisher eventPublisher;
    private final SysTaskLogService logService;
    private final SysTaskDetailService detailService;
    private final TaskConfig taskConfig;
    private final FileStorage fileStorage;

    public TaskListener(SysTaskLogService logService,
                        SysTaskDetailService detailService,
                        TaskConfig taskConfig,
                        FileStorage fileStorage,
                        ApplicationEventPublisher eventPublisher
    ) {
        this.eventPublisher = eventPublisher;
        this.logService = logService;
        this.detailService = detailService;
        this.taskConfig = taskConfig;
        this.fileStorage = fileStorage;
    }

    @SneakyThrows
    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        //执行前
        JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
        String name = jobKey.getName();
        //
        Task task = (Task) jobExecutionContext.getScheduler().getContext().get(TaskUtil.TASK_TASK_NAME + name);
        task.setRunStatus(TaskRunStatus.RUNNING);
        task.setRunTimeStart(jobExecutionContext.getFireTime());
        task.setRunTimeEnd(null);
        task.setRunRemarks("");
        //
        if (log.isInfoEnabled()) {
            log.info("Task " + jobKey.getName() + " started");
        }
        //
        String runId = UUID.randomUUID().toString().replaceAll("-", "");
        jobExecutionContext.put(TaskUtil.TASK_RUN_ID_NAME, runId);
        //
        if (jobExecutionContext.getNextFireTime() != null)
            task.setNextTime(jobExecutionContext.getNextFireTime());
        if (jobExecutionContext.getPreviousFireTime() != null)
            task.setPrevTime(jobExecutionContext.getPreviousFireTime());
        if (jobExecutionContext.getScheduledFireTime() != null)
            task.setScheTime(jobExecutionContext.getScheduledFireTime());
        //
        this.eventPublisher.publishEvent(new TaskStartedEvent(name, runId, task.getRunTimeStart()
                , jobExecutionContext.getMergedJobDataMap()));
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        //否定执行
    }

    @SneakyThrows
    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        String runId = (String) jobExecutionContext.get(TaskUtil.TASK_RUN_ID_NAME);
        TaskLogger logger = (TaskLogger) jobExecutionContext.get(TaskUtil.TASK_RUN_LOG_NAME);

        //否定完成
        JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
        String name = jobKey.getName();

        //
        Task task = (Task) jobExecutionContext.getScheduler().getContext().get(TaskUtil.TASK_TASK_NAME + name);

        //
        if (e == null) {
            Integer resultCode = (Integer) jobExecutionContext.get(TaskUtil.TASK_RUN_RESULT);
            if (resultCode == null) {
                //成功
                task.setRunStatus(TaskRunStatus.SUCCESS);
            } else if (0 == resultCode) {
                //成功
                task.setRunStatus(TaskRunStatus.SUCCESS);
            } else {
                //成功，但有部份异常
                task.setRunStatus(TaskRunStatus.ABNORMAL);
            }
            task.setRunTimeEnd(new Date());
            task.setRunRemarks("");
        } else {
            String message = e.getMessage();
            if (message.length() > 1000) {
                message = message.substring(0, 950) + "...";
            }
            task.setRunStatus(TaskRunStatus.FAILED);
            task.setRunRemarks(message);
        }

        //
        SysTaskJob job = (SysTaskJob) jobExecutionContext.get(TaskUtil.TASK_TASK_JOB_NAME);
        if (job == null) {
            log.warn("No record run log, because " + TaskUtil.TASK_TASK_JOB_NAME + " not in DataMap");
            return;
        }

        //
        String cron = "";
        Trigger trigger = jobExecutionContext.getTrigger();
        if (trigger != null) {
            JobDataMap keyMap = trigger.getJobDataMap();
            Object o = keyMap.get(TaskUtil.TASK_RULE_NAME);
            if (o != null) {
                cron = String.valueOf(o);
            }
        }

        //
        String param = "";
        Map map = (Map) jobExecutionContext.get(TaskUtil.TASK_PARAM_NAME);
        if (map != null && map.size() > 0) {
            param = JsonUtil.toJsonString(map);
        }

        //日志转存
        //本地文件前缀 file://
        //转存文件前缀 storage://
        String runLog = logger != null && logger.hasContent() ? logger.getPath() : null;
        if (StringUtils.hasText(runLog)) {
            runLog = runLog.replace("\\", "/");
            if (new File(runLog).exists()) {
                if (StringUtils.hasText(taskConfig.getStorePath())) {
                    String storagePath = taskConfig.getStorePath() + runLog.substring(taskConfig.getLogPath().length());
                    try (FileInputStream stream = new FileInputStream(runLog)) {
                        this.fileStorage.save(stream, storagePath);
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                    new File(runLog).delete();
                    //转存地址
                    runLog = "storage://" + storagePath;
                } else if (taskConfig.getStoreDb() == 1) {
                    String s;
                    try (FileInputStream stream = new FileInputStream(runLog)) {
                        s = IOUtils.toString(stream, "UTF-8");
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                    //
                    if (StringUtils.hasText(s)) {
                        SysTaskDetail detail = new SysTaskDetail();
                        detail.setId(runId);
                        detail.setDetail(s);
                        detailService.add(detail);
                    }
                    //
                    new File(runLog).delete();
                    //
                    runLog = "db://" + runId;
                } else {
                    //转存地址
                    runLog = "file://" + runLog;
                }
            } else {
                log.warn("runLog " + runLog + " not exists, skip save to db");
                runLog = null;
            }
        }

        //add log
        SysTaskLog taskLog = new SysTaskLog();
        taskLog.setId(runId);
        taskLog.setTaskId(job.getId());
        taskLog.setName(name);
        taskLog.setCreateTime(new Date());
        taskLog.setActuator(job.getActuator());
        taskLog.setSchedule(cron);
        taskLog.setParam(param);

        taskLog.setRunTimeStart(task.getRunTimeStart());

        taskLog.setRunStatus(task.getRunStatus());
        taskLog.setRunRemarks(task.getRunRemarks());
        taskLog.setRunTimeEnd(task.getRunTimeEnd());

        // taskLog.setRunHostname(TaskUtil.hostname());
        taskLog.setRunEnvName(String.valueOf(jobExecutionContext.get(TaskUtil.TASK_TASK_RUN_ENV)));
        taskLog.setRunHostname(String.valueOf(jobExecutionContext.get(TaskUtil.TASK_TASK_RUN_HOST)));

        taskLog.setLogPath(runLog);

        logService.add(taskLog);

        //
        if (log.isInfoEnabled()) {
            log.info("Task " + jobKey.getName() + " finished");
        }

        //
        this.eventPublisher.publishEvent(new TaskFinishedEvent(name, runId,
                taskLog.getRunStatus(), taskLog.getRunTimeStart(), task.getRunTimeEnd(),
                taskLog.getRunRemarks(),
                jobExecutionContext.getMergedJobDataMap()));
    }
}
